// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace coin2::exchange::mexc::feed_v3::impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class MexcFeedUpdateParser {
 public:
  static bool ParseTicker(
      BookBuilder* book_builder,
      const rapidjson::Value& book_json,
      int64_t ts) {
    auto tx = book_builder->NewTransaction(ts);
    tx.Clear();
    if (book_json["a"].GetStringLength() != 0) {
      tx.UpdateAsk(stod(book_json["a"].GetString()), stod(book_json["A"].GetString()));
    }
    if (book_json["b"].GetStringLength() != 0) {
      tx.UpdateBid(stod(book_json["b"].GetString()), stod(book_json["B"].GetString()));
    }
    return true;
  }

  static bool ParseSnapshot(
      BookBuilder* book_builder,
      const rapidjson::Value& book_json,
      int64_t ts) {
    auto tx = book_builder->NewTransaction(ts);
    tx.Clear();
    if (book_json.HasMember("asks")) {
      for (const auto& tick : book_json["asks"].GetArray()) {
        tx.UpdateAsk(stod(tick["p"].GetString()),
                    stod(tick["v"].GetString()));
      }
    }
    if (book_json.HasMember("bids")) {
      for (const auto& tick : book_json["bids"].GetArray()) {
        tx.UpdateBid(stod(tick["p"].GetString()),
                    stod(tick["v"].GetString()));
      }
    }
    return true;
  }

  static bool ParseDiff(
      BookBuilder* book_builder,
      const rapidjson::Value& book_json,
      int64_t ts) {
    auto tx = book_builder->NewTransaction(ts);
    if (book_json.HasMember("asks")) {
      for (const auto& tick : book_json["asks"].GetArray()) {
        tx.UpdateAsk(stod(tick["p"].GetString()),
                    stod(tick["v"].GetString()));
      }
    }
    if (book_json.HasMember("bids")) {
      for (const auto& tick : book_json["bids"].GetArray()) {
        tx.UpdateBid(stod(tick["p"].GetString()),
                    stod(tick["v"].GetString()));
      }
    }
    return true;
  }

  static void ParseTrade(
      const rapidjson::Value& trade_json,
      Trade* trade) {
    trade->timestamp = trade_json["t"].GetInt64() * 1'000'000LL;  // ms -> ns
    trade->side = ParseTradeSide(trade_json["S"].GetInt());
    trade->price = stod(trade_json["p"].GetString());
    trade->fill_qty = stod(trade_json["v"].GetString());
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1m", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"3m", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"5m", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15m", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30m", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"60m", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1h", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1d", fastfeed::proto::KLINE_INTERVAL_1DAY}
    };

    auto iter = interval_map.find(std::string(interval));
    if (iter == interval_map.end()) {
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static bool ParseKlineRest(
      const rapidjson::Value& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {
    const auto& v = msg.GetArray();
    CHECK_EQ(v.Size(), 8);
    int64_t open_timestamp = v[0].GetInt64() * 1'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();
    kline->open_timestamp = open_timestamp;
    kline->open = stod(v[1].GetString());
    kline->high = stod(v[2].GetString());
    kline->low = stod(v[3].GetString());
    kline->close = stod(v[4].GetString());
    kline->close_timestamp = v[6].GetInt64() * 1'000'000LL;
    kline->volume = stod(v[5].GetString());
    kline->turnover = stod(v[7].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;

    return freezed;
  }

 private:
  static coin2::exchange::feed::TradeSide ParseTradeSide(int side) {
    if (side == 1) {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == 2) {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else {
      throw std::runtime_error("[Mexc Feed Processor] unknown side: " + side);
    }
  }
};

}  // namespace namespace coin2::exchange::mexc::feed_v3::impl

